package HTTP

import (
	"SQL/FSDB"
	"fmt"
	"logs"
	"net/http"
	"sync"

	"strconv"

	"time"

	"github.com/gorilla/websocket"
)

/*
websocket建立过程概要：
	1.initWebSocket
	2.addClient
	3.deleteClient (掉线)
	4.close (同账号登录)


消息分发过程概要：
	1.user1	-> user2
			-> db
	2.user1	-> group<all members>(不管在不在线)
			-> db
*/

const (
	MsgTypeFriend = 1
	MsgTypeGroup  = 2
	MsgTypeSystem = 3
)

// ClientMsg 客户端发送到服务端的消息
// - ReceiverID:消息发送ID
// - ReceiverID:消息接收者ID，无论是群还是用户，皆为Receiver
// - IsGroup:消息接收者是否为群组
// - Message:消息具体内容
type ClientMsg struct {
	SenderID   string `json:"SenderID"`
	ReceiverID string `json:"ReceiverID"`
	IsGroup    bool   `json:"IsGroup"`
	Message    string `json:"Message"`
}

// ServerMsg 服务端发送到客户端的消息
// - Type:消息类型：1.用户消息 2.群消息 3.后端系统消息
// - Content:消息具体内容
type ServerMsg struct {
	Type    int         `json:"Type"`
	Content interface{} `json:"Content"`
}

type FriendMsg struct {
	FriendID   string `json:"FriendID"`
	FriendName string `json:"FriendName"`
	FriendIcon string `json:"FriendIcon"`
	MsgID      string `json:"MsgID"`
	Msg        string `json:"Msg"`
	CreateTime string `json:"CreateTime"`
}

type GroupMsg struct {
	GroupID    string `json:"GroupID"`
	GroupName  string `json:"GroupName"`
	GroupIcon  string `json:"GroupIcon"`
	SenderID   string `json:"SenderID"`
	SenderName string `json:"SenderName"`
	SenderIcon string `json:"SenderIcon"`
	MsgID      string `json:"MsgID"`
	Msg        string `json:"Msg"`
	CreateTime string `json:"CreateTime"`
}

type SystemMsg struct {
	Code       int    `json:"Code"`
	Msg        string `json:"Msg"`
	CreateTime string `json:"CreateTime"`
}

const (
	noticeOffline    = 251
	noticeMsgSent    = 252
	noticeLogin      = 253
	noticeForceQuit  = 254
	noticeLoginAgain = 255
)
const (
	mOffline    = "OFFLINE"
	mLogin      = "LOGIN SUCCESS"
	mForceQuit  = "FORCE QUIT"
	mLoginAgain = "TWO CLIENTS LOGIN"
)

var onlineUsers = make(map[string]*websocket.Conn)
var dispatchChan = make(chan ClientMsg)
var upgrader = websocket.Upgrader{}
var lock = sync.Mutex{}

// --------------------------------------------- 连接创建、消息创建部分
// initWebSocket 初始化WebSocket连接，
// 				 每个客户端都需要连接/ws接口以建立WebSocket连接
func initWebSocket(w http.ResponseWriter, r *http.Request) {

	var resCode = http.StatusOK
	var msg = ""

	upgrader.CheckOrigin = func(r *http.Request) bool { //关闭Origin检查
		return true
	}
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		println("*** WebSocket CONNECT FAILED!\nerr:", err.Error())
		return
	}

	defer func() {
		if ws != nil {
			ws.WriteJSON(httpResult{resCode, msg, nil}) // TODO: FICOW 修改httpResult为ServerMsg
			ws.Close()
		}
	}()

	userID := r.FormValue("userID")
	password := r.FormValue("password")

	ok := false
	if ok, resCode, msg = isArgsEmpty(userID, password); ok {
		return
	}
	if ok, resCode, msg = isValidID(userID, "UserID"); !ok {
		return
	}
	if ok, resCode, msg = isValidPwd(password); !ok {
		return
	}

	errCode, errMsg, _ := checkLogin(userID, password)
	if errCode != Success {
		msg = errMsg
		resCode = errCode
		logs.Print(userID, " login FAILED!")
		return
	}

	clientID := userID
	if !addClient(userID, ws) {
		dispatchSystemMsgToUser(clientID, noticeLoginAgain, mLoginAgain, "") //告知其在顶别人下线
	} else {
		dispatchSystemMsgToUser(clientID, noticeLogin, mLogin, "")
		logs.Print(clientID, mLogin)
	}

	// --------------------------------------------- 当前用户的消息发送循环
	for {
		var message ClientMsg
		err := ws.ReadJSON(&message)
		if err != nil {
			logs.Print("clientID:", clientID, "DISCONNECTED!\n	err:", err.Error())
			deleteClient(clientID)
			break
		}
		// 更新当前用户的在线时间
		go updateUserOnlineTime(clientID, currentTime())

		// 消息有效性检查
		if ok, errCode, errMsg := isValidMsg(clientID, message); !ok {
			go dispatchSystemMsgToUser(clientID, errCode, errMsg, "")
			continue
		}
		// 用户是否在线
		if !message.IsGroup {
			if !isOnline(message.ReceiverID) {
				go dispatchSystemMsgToUser(clientID, noticeOffline, mOffline, "") // 提示用户消息的接收者不在线
			}
		}

		dispatchChan <- message // 派发消息
	}
}
func updateUserOnlineTime(ID string, time string) {
	updateTimeSQL := `UPDATE users
					  SET lastOnlineTime = $1
					  WHERE id = $2`
	_, err := FSDB.Exec(updateTimeSQL, time, ID)
	if err != nil {
		logs.PrintParent("updateUserOnlineTime Exec，err:", err.Error())
		return
	}
}

// 消息有效性检查
func isValidMsg(userID string, message ClientMsg) (ok bool, errCode int, errMsg string) {

	if message.SenderID != userID {
		return false, errInvalidSender, mInvalidSender
	}
	if message.ReceiverID == userID {
		return false, errInvalidReceiver, mInvalidReceiver
	}
	if message.IsGroup {
		if ok, errCode, errMsg = isValidID(message.ReceiverID, "GroupID"); ok {
			return true, Success, ""
		}
		if errCode == errInvalidID {
			errMsg = mInvalidGroupID
		}
		return
	}
	if ok, errCode, errMsg = isValidID(message.ReceiverID, "UserID"); !ok {
		if errCode == errInvalidID {
			errMsg = "ReceiverID参数不能为空，请检查参数名是否匹配！"
			return
		}
		errMsg = "ReceiverID不存在！"
		return
	}
	msgLen := len(message.Message)
	if msgLen == 0 || msgLen > 2048 {
		return false, errInvalidMsg, mInvalidMsg
	}
	return true, Success, ""
}

// 是否在线
func isOnline(clientID string) bool {
	_, isOnline := onlineUsers[clientID]
	return isOnline
}

// --------------------------------------------- 连接管理部分

// 增加WebSocket连接
func addClient(clientID string, con *websocket.Conn) (canAdd bool) {

	if isOnline(clientID) {
		dispatchSystemMsgToUser(clientID, noticeForceQuit, mForceQuit, "")
		deleteClient(clientID)
		time.Sleep(time.Millisecond) // 如果不睡眠或者睡眠时间太短，两个连接都会被关掉
		logs.Print(clientID, mForceQuit)
		addConnection(clientID, con)
		return false
	}
	addConnection(clientID, con)
	return true
}
func addConnection(clientID string, con *websocket.Conn) {
	lock.Lock()
	onlineUsers[clientID] = con
	lock.Unlock()
}

// 删除WebSocket连接
func deleteClient(clientID string) {
	if !isOnline(clientID) {
		return
	}
	onlineUsers[clientID].Close()
	lock.Lock()
	delete(onlineUsers, clientID)
	lock.Unlock()
}

// --------------------------------------------- 消息分发部分

// 消息分发核心
func dispatchMessages() {
	for {
		message := <-dispatchChan

		// TODO: COMMENT
		//logs.Print(message.SenderID, " -> ", message.ReceiverID, " (", message.Message, ")")
		var (
			msgID         string
			msgCreateTime string
		)
		errCode, errMsg := writeMsgToDB(message.SenderID, message.ReceiverID, message.Message, &msgID, &msgCreateTime)
		if errCode != Success {
			go dispatchSystemMsgToUser(message.SenderID, errCode, errMsg, "")
			return
		}
		// 提示消息发送成功
		go dispatchSystemMsgToUser(message.SenderID, noticeMsgSent, msgID, msgCreateTime)

		if message.IsGroup {
			go dispatchGroupMsgToUser(message, msgID, msgCreateTime)
		} else {
			go dispatchFriendMsgToUser(message, msgID, msgCreateTime)
		}
	}
}

// 将系统消息发送给当前用户
func dispatchSystemMsgToUser(userID string, errCode int, errMsg string, time string) {

	receiver := userID
	client, _ := onlineUsers[receiver]
	res := packedSystemMsg(errCode, errMsg, time)
	err := client.WriteJSON(res)
	if err != nil {
		logs.Print(receiver, "DISCONNECTED!\n err:", err.Error())
		client.Close()
		deleteClient(receiver)
	}
}

// 将好友消息发送给好友
func dispatchFriendMsgToUser(msg ClientMsg, msgID string, createTime string) {

	receiver := msg.ReceiverID
	client, isOnline := onlineUsers[receiver]

	if !isOnline {
		return
	}

	// 目前阶段，忽视发送成功与否
	go func() {
		errCode, errMsg, senderName, senderIcon := objectInfoFromDB(msg.SenderID)
		if errCode != Success {
			logs.Print("dispatchFriendMsgToUser", errMsg)
			return
		}
		res := packedFriendMsg(msg.SenderID, senderName, senderIcon, msgID, msg.Message, createTime)
		err := client.WriteJSON(res)
		if err != nil {
			logs.Print(receiver, "DISCONNECTED!\n err:", err.Error())
			client.Close()
			deleteClient(receiver)
			return
		}
		//更新好友在线时间
		updateUserOnlineTime(receiver, createTime)
	}()
}

// 将消息发送给群里的每个人，不管其在不在，如果在，就可以收到
func dispatchGroupMsgToUser(msg ClientMsg, msgID string, createTime string) {

	var (
		senderID = msg.SenderID
		groupID  = msg.ReceiverID
	)
	groupMemberSQL := `SELECT userid AS memberID FROM members WHERE groupid=$1 AND userid <> $2`
	members, err := FSDB.Query(groupMemberSQL, groupID, senderID)
	defer members.Close()

	if err != nil {
		logs.Print("dispatchGroupMsgToUser Query FAILED!\nerr:", err.Error())
		return
	}
	errCode, errMsg, groupName, groupIcon := objectInfoFromDB(groupID)
	if errCode != Success {
		logs.Print("dispatchGroupMsgToUser ", errMsg)
		return
	}
	errCode, errMsg, senderName, senderIcon := objectInfoFromDB(senderID)
	if errCode != Success {
		logs.Print("dispatchGroupMsgToUser objectInfoFromDB(senderID) FAILED!")
		return
	}

	for members.Next() {
		var id int64
		err := members.Scan(&id)
		memberID := fmt.Sprintf("%v", id)

		if err != nil {
			logs.Print("dispatchMsgToGroup members.Scan() FAILED!\nerr:", err.Error())
			return
		}
		client, isOnline := onlineUsers[memberID]
		if !isOnline {
			return
		}

		go func() {
			res := packedGroupMsg(groupID, groupName, groupIcon, senderID, senderName, senderIcon, msgID, msg.Message, createTime)
			err = client.WriteJSON(res)
			if err != nil {
				logs.Print(memberID, "is NOT CONNECTED!\n err:", err.Error())
				client.Close()
				deleteClient(memberID)
				return
			}
			// 更新此群成员的在线时间
			updateUserOnlineTime(memberID, createTime)
		}()
	}
}

// 消息写入数据库
func writeMsgToDB(userID string, talkerID string, msg string, msgID *string, createTime *string) (errCode int, errMsg string) {

	errCode = errEndTx
	tx, err := FSDB.BeginTx()
	defer func() {
		if errCode != Success {
			err := tx.Rollback()
			if err != nil {
				errCode = errEndTx
				errMsg = "writeMsgToDB Rollback执行错误，" + err.Error()
			}
		}
	}()
	if err != nil {
		errMsg = "writeMsgToDB BeginTx执行错误，" + err.Error()
		return
	}
	sendMsgSQL := `INSERT INTO messages (sender,receiver,content) 
					VALUES ($1,$2,$3);`
	_, err = FSDB.ExecTx(tx, sendMsgSQL, userID, talkerID, msg)
	if err != nil {
		errMsg = "sendMsgSQL ExecTx执行错误，" + err.Error()
		return
	}

	lastInsertIDSQL := `SELECT currval(pg_get_serial_sequence('messages', 'id'));`
	liRows, err := tx.Query(lastInsertIDSQL)
	defer liRows.Close()
	if err != nil {
		errMsg = "lastInsertIDSQL Query执行错误，" + err.Error()
		return
	}
	var currval = -1
	for liRows.Next() {
		err := liRows.Scan(&currval)
		if err != nil {
			errMsg = "lastInsertIDSQL rows.Scan执行错误，" + err.Error()
			return
		}
	}
	liRows.Close()
	if currval == -1 {
		errMsg = "创建消息失败！未能获取最新消息的ID。"
		return
	}

	*msgID = strconv.Itoa(currval)

	msgCreateTimeSQL := `
				SELECT createTime FROM messages WHERE id = $1;
				`
	mctStmt, mctRows, err := FSDB.QueryTx(tx, msgCreateTimeSQL, currval)
	defer mctStmt.Close()
	defer mctRows.Close()
	if err != nil {
		errMsg = "msgCreateTimeSQL QueryTx执行错误，"
		return
	}
	for mctRows.Next() {
		err = mctRows.Scan(createTime)
		if err != nil {
			errMsg = "mctRows.Scan执行错误，"
			return
		}
	}
	tx.Commit()

	return Success, "消息发送成功！"
}

// 获取资料
func objectInfoFromDB(ID string) (errCode int, errMsg string, name string, icon string) {

	errCode = Success
	senderInfoSQL := `
				SELECT nickname,icon FROM objects WHERE id = $1;
				`
	siRows, err := FSDB.Query(senderInfoSQL, ID)
	defer siRows.Close()
	if err != nil {
		errCode = errDBQueryFailed
		errMsg = "msgCreateTimeSQL QueryTx执行错误，"
		logs.Print(errMsg, err.Error())
		return
	}
	for siRows.Next() {
		err = siRows.Scan(&name, &icon)
		if err != nil {
			errCode = errRowsScan
			errMsg = "mctRows.Scan执行错误，"
			logs.Print(errMsg, err.Error())
			return
		}
	}
	return
}
func packedSystemMsg(errCode int, errMsg string, time string) ServerMsg {
	if time == "" {
		time = currentTime()
	}
	return ServerMsg{
		Type: MsgTypeSystem,
		Content: SystemMsg{
			Code:       errCode,
			Msg:        errMsg,
			CreateTime: time,
		},
	}
}
func packedFriendMsg(friendID string, friendName string, friendIcon string, msgID string, msg string, createTime string) ServerMsg {
	return ServerMsg{
		Type: MsgTypeFriend,
		Content: FriendMsg{
			FriendID:   friendID,
			FriendName: friendName,
			FriendIcon: friendIcon,
			MsgID:      msgID,
			Msg:        msg,
			CreateTime: createTime,
		},
	}
}
func packedGroupMsg(gID string, gName string, gIcon string, sID string, sName string, sIcon string, msgID string, msg string, createTime string) ServerMsg {
	return ServerMsg{
		Type: MsgTypeGroup,
		Content: GroupMsg{
			GroupID:    gID,
			GroupName:  gName,
			GroupIcon:  gIcon,
			SenderID:   sID,
			SenderName: sName,
			SenderIcon: sIcon,
			MsgID:      msgID,
			Msg:        msg,
			CreateTime: createTime,
		},
	}
}
